/*
 * Copyright (C) 2005,2006,2007 MaNGOS <http://www.mangosproject.org/>
 * Copyright (C) 2008 Arcemu Team <http://www.arcemu.org/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <G3D/Vector3.h>
#include <G3D/Triangle.h>

#include "TileAssembler.h"
#include "CoordModelMapping.h"
#include "ModelContainer.h"

#include <limits.h>
#include <string.h>

#ifdef _ASSEMBLER_DEBUG
FILE *g_df = NULL;
#endif

using namespace G3D;

namespace VMAP
{
	//=================================================================

	Vector3 ModelPosition::transform(const Vector3& pIn) const
	{
		//return(pIn);
		Vector3 out = pIn * iScale;
		out = izMatrix * out;
		out = ixMatrix * out;
		out = iyMatrix * out;
		return(out);

	}
	//=================================================================

	TileAssembler::TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName)
	{
		iCurrentUniqueNameId = 0;
		iFilterMethod = NULL;
		iSrcDir = pSrcDirName;
		iDestDir = pDestDirName;
		//mkdir(iDestDir);
		init();
	}

	//=================================================================

	TileAssembler::~TileAssembler()
	{
		delete iCoordModelMapping;
	}

	//=================================================================

	void TileAssembler::init()
	{
		iCoordModelMapping = new CoordModelMapping();
		addWorldAreaMapId(0);      //Azeroth
		addWorldAreaMapId(1);      //Kalimdor
		addWorldAreaMapId(530);    //Expansion01
		addWorldAreaMapId(571);    //Expansion02
	}
	//=================================================================

	std::string getModNameFromModPosName(const std::string& pModPosName)
	{
		size_t spos = pModPosName.find_first_of('#');
		std::string modelFileName = pModPosName.substr(0,spos);
		return(modelFileName);
	}

	//=================================================================

	unsigned int TileAssembler::getUniqueNameId(const std::string pName)
	{
		unsigned int result;

		if(!iUniqueNameIds.containsKey(pName))
		{
			++iCurrentUniqueNameId;
			iUniqueNameIds.set(pName, iCurrentUniqueNameId);
		}
		result = iUniqueNameIds.get(pName);
		return result;
	}

	//=================================================================

	std::string TileAssembler::getDirEntryNameFromModName(unsigned int pMapId, const std::string& pModPosName)
	{
		size_t spos;
		char buffer[20];

		std::string modelFileName = getModNameFromModPosName(pModPosName);
		//std::string fext = pModPosName.substr(modelFileName.length(),pModPosName.length());
		unsigned int fextId = getUniqueNameId(pModPosName);
		sprintf(buffer, "_%07d",fextId);
		std::string fext(buffer);
		spos = modelFileName.find_last_of('/');
		std::string fname = modelFileName.substr(spos+1, modelFileName.length());
		spos = fname.find_last_of('.');
		fname = fname.substr(0,spos);
		sprintf(buffer, "%03u", pMapId);
		std::string dirEntry(buffer);
		dirEntry.append("_");
		dirEntry.append(fname);
		dirEntry.append(fext);
		dirEntry.append(".vmap");
		return(dirEntry);
	}

	//=================================================================

	void emptyArray(Array<ModelContainer*>& mc)
	{
		int no=mc.size();
		while(no > 0)
		{
			--no;
			delete mc[no];
			mc.remove(no);
		}
	}

	//=================================================================
	bool TileAssembler::convertWorld()
	{
		#ifdef _ASSEMBLER_DEBUG
		#   ifdef _DEBUG
		::g_df = fopen("../TileAssembler_debug.txt", "wb");
		#   else
		::g_df = fopen("../TileAssembler_release.txt", "wb");
		#   endif
		#endif

		bool result = true;
		printf("Reading coordinate mappings...\n");
		std::string fname = iSrcDir;
		fname.append("/");
		fname.append("dir");
		iCoordModelMapping->setModelNameFilterMethod(iFilterMethod);
		iCoordModelMapping->readCoordinateMapping(fname);

		Array<unsigned int> mapIds = iCoordModelMapping->getMaps();
		if(mapIds.size() == 0)
		{
			result = false;
		}
		for(int i=0; i<mapIds.size() && result; ++i)
		{
			unsigned int mapId = mapIds[i];
			printf("Converting map %lu...\n", mapId);

			#ifdef _ASSEMBLER_DEBUG
			if(mapId == 0)								  // "Azeroth" just for debug
			{
				for(int x=28; x<29 && result; ++x)		  //debug
				{
					for(int y=28; y<29 && result; ++y)
					{
						#else
						// ignore DeeprunTram (369) it is too large for short vector and not important
						// ignore test (13), Test (29) , development (451)
						if(mapId != 369 && mapId != 13 && mapId != 29 && mapId != 451)
						{
							for(int x=0; x<66 && result; ++x)
							{
								for(int y=0; y<66 && result; ++y)
								{
									#endif
									printf("Converting cell [%lu][%lu] on map %lu...\n", x, y, mapId);
									Array<ModelContainer*> mc;
									std::string dirname;
									char buffer[100];
									if(iCoordModelMapping->isWorldAreaMap(mapId) && x<65 && y<65)
									{
										sprintf(buffer, "%03u_%d_%d",mapId,x,y); // Let's flip x and y here
										dirname = std::string(buffer);
									}
									else
									{
										sprintf(buffer, "%03u",mapId);
										dirname = std::string(buffer);
									}
									result = fillModelContainerArray(dirname, mapId, x, y, mc);
									emptyArray(mc);
								}
							}
						}
					}
					#ifdef _ASSEMBLER_DEBUG
					if(::g_df) fclose(::g_df);
					#endif

					return result;
				}

				//=================================================================

				bool TileAssembler::fillModelContainerArray(const std::string& pDirFileName, unsigned int pMapId, int pXPos, int pYPos, Array<ModelContainer*>& pMC)
				{
					bool result = true;
					ModelContainer* modelContainer;

					NameCollection nameCollection = iCoordModelMapping->getFilenamesForCoordinate(pMapId, pXPos, pYPos);
					if(nameCollection.size() > 0)
					{
						result = false;
						char dirfilename[500];
						sprintf(dirfilename,"%s/%s.vmdir",iDestDir.c_str(),pDirFileName.c_str());
						FILE *dirfile = fopen(dirfilename, "ab");
						if(dirfile)
						{
							result = true;
							char destnamebuffer[500];
							char fullnamedestnamebuffer[500];
							if(nameCollection.iMainFiles.size() >0)
							{
								sprintf(destnamebuffer,"%03u_%i_%i.vmap",pMapId, pXPos, pYPos); // flip it here too
								std::string checkDoubleStr = std::string(dirfilename);
								checkDoubleStr.append("##");
								checkDoubleStr.append(std::string(destnamebuffer));
								// Check, if same file already is in the same dir file
								if(!iCoordModelMapping->isAlreadyProcessedSingleFile(checkDoubleStr))
								{
									iCoordModelMapping->addAlreadyProcessedSingleFile(checkDoubleStr);
									fprintf(dirfile, "%s\n",destnamebuffer);
									sprintf(fullnamedestnamebuffer,"%s/%s",iDestDir.c_str(),destnamebuffer);
									modelContainer = processNames(nameCollection.iMainFiles, fullnamedestnamebuffer);
									if(modelContainer)
									{
										pMC.append(modelContainer);
									}
									else
									{
										result = false;
									}
								}
							}
							// process the large singe files
							int pos = 0;
							while(result && (pos < nameCollection.iSingeFiles.size()))
							{
								std::string destFileName = iDestDir;
								destFileName.append("/");
								std::string dirEntryName = getDirEntryNameFromModName(pMapId,nameCollection.iSingeFiles[pos]);
								std::string checkDoubleStr = std::string(dirfilename);
								checkDoubleStr.append("##");
								checkDoubleStr.append(nameCollection.iSingeFiles[pos]);
								// Check, if same file already is in the same dir file
								if(!iCoordModelMapping->isAlreadyProcessedSingleFile(checkDoubleStr))
								{
									iCoordModelMapping->addAlreadyProcessedSingleFile(checkDoubleStr);
									fprintf(dirfile, "%s\n",dirEntryName.c_str());
									destFileName.append(dirEntryName);

									Array<std::string> positionarray;
									positionarray.append(nameCollection.iSingeFiles[pos]);

									if(!iCoordModelMapping->isAlreadyProcessedSingleFile(nameCollection.iSingeFiles[pos]))
									{
										modelContainer = processNames(positionarray, destFileName.c_str());
										iCoordModelMapping->addAlreadyProcessedSingleFile(nameCollection.iSingeFiles[pos]);
										if(modelContainer)
										{
											pMC.append(modelContainer);
										}
										else
										{
											result = false;
										}
									}
								}
								++pos;
							}
							fclose(dirfile);
						}
					}
					return(result);
				}

				//=================================================================

				void removeEntriesFromTree(AABSPTree<SubModel *>* pTree)
				{
					Array<SubModel *> submodelArray;
					pTree->getMembers(submodelArray);
					int no = submodelArray.size();
					while(no > 0)
					{
						--no;
						delete submodelArray[no];
					}
				}

				//=================================================================

				ModelContainer* TileAssembler::processNames(const Array<std::string>& pPositions, const char* pDestFileName)
				{
					ModelContainer *modelContainer = 0;

					Vector3 basepos = Vector3(0,0,0);
					AABSPTree<SubModel *>* mainTree = new AABSPTree<SubModel *>();

					int pos = 0;

					bool result = true;
					while(result && (pos < pPositions.size()))
					{
						std::string modelPosString = pPositions[pos];
						std::string modelFileName = getModNameFromModPosName(modelPosString);

						if(!fillModelIntoTree(mainTree, basepos, modelPosString, modelFileName))
						{
							result = false;
							break;
						}
						++pos;
					}
					if(result && mainTree->size() > 0)
					{
						mainTree->balance();
						modelContainer = new ModelContainer(mainTree);
						modelContainer->writeFile(pDestFileName);
					}
					removeEntriesFromTree(mainTree);

					delete mainTree;

					return(modelContainer);
				}

				//=================================================================
				bool TileAssembler::readRawFile(std::string& pModelFilename,  ModelPosition& pModelPosition, AABSPTree<SubModel *> *pMainTree)
				{
					bool result = false;

					std::string filename = iSrcDir;
					if(filename.length() >0)
						filename.append("/");
					filename.append(pModelFilename);
					FILE *rf = fopen(filename.c_str(), "rb");
					if(!rf)
					{
						// depending on the extractor version, the data could be located in the root dir
						std::string baseModelFilename = pModelFilename.substr((pModelFilename.find_first_of("/")+1),pModelFilename.length());
						filename = iSrcDir;
						if(filename.length() >0)
							filename.append("/");
						filename.append(baseModelFilename);
						rf = fopen(filename.c_str(), "rb");
					}
					char ident[8];

					int trianglecount =0;

					#ifdef _ASSEMBLER_DEBUG
					int startgroup = 0;					 //2;
					int endgroup = INT_MAX;				 //2;
					fprintf(::g_df,"-------------------------------------------------\n");
					fprintf(::g_df,"%s\n", pModelFilename.c_str());
					fprintf(::g_df,"-------------------------------------------------\n");
					#else
					int startgroup = 0;
					int endgroup = INT_MAX;
					#endif

					if(rf)
					{
						if(fread(&ident, 8, 1, rf) != 1) { fclose(rf); return(false); }
						if(strcmp(ident, "VMAP001") == 0)
						{
							// OK, do nothing
						}
						else if(strcmp(ident, "VMAP002") == 0)
						{
							// we have to read one int. This is needed during the export and we have to skip it here
							int tempNVectors;
							if(fread(&tempNVectors, sizeof(int), 1, rf) != 1) { fclose(rf); return(false); }

						}
						else
						{
							// wrong version
							fclose(rf);
							return(false);
						}
						uint32 groups;
						char blockId[5];
						blockId[4] = 0;
						int blocksize;

						if(fread(&groups, sizeof(uint32), 1, rf) != 1) { fclose(rf); return(false); }

						for(int g=0;g<(int)groups;g++)
						{
							// group MUST NOT have more then 65536 indexes !! Array will have a problem with that !! (strange ...)
							Array<int> tempIndexArray;
							Array<Vector3> tempVertexArray;

							AABSPTree<Triangle> *gtree = new AABSPTree<Triangle>();

							uint32 flags;
							uint32 isindoor = 0;
							uint32 branches;
							if(fread(&flags, sizeof(uint32), 1, rf) != 1)
							{
								fclose(rf);
								return(false);
							}

							if(flags & 1) {
								if(fread(&isindoor, sizeof(uint32), 1, rf) != 1)
								{
									fclose(rf);
									return(false);
								}
							}

							if(fread(&blockId, 4, 1, rf) != 1) { fclose(rf); return(false); }
							if(strcmp(blockId, "GRP ") != 0)
							{
								if(fread(&blockId, 4, 1, rf) != 1) { fclose(rf); return(false); }
							}
							if(strcmp(blockId, "GRP ") != 0) { printf("BUG: (%s)\n",blockId); fclose(rf); return(false); }
							if(fread(&blocksize, sizeof(int), 1, rf) != 1) { fclose(rf); return(false); }
							if(fread(&branches, sizeof(uint32), 1, rf) != 1) { fclose(rf); return(false); }
							for(int b=0;b<(int)branches; b++)
							{
								uint32 indexes;
								// indexes for each branch (not used jet)
								if(fread(&indexes, sizeof(uint32), 1, rf) != 1) { fclose(rf); return(false); }
							}

							// ---- indexes
							if(fread(&blockId, 4, 1, rf) != 1) { fclose(rf); return(false); }
							if(strcmp(blockId, "INDX") != 0) { fclose(rf); return(false); }
							if(fread(&blocksize, sizeof(int), 1, rf) != 1) { fclose(rf); return(false); }
							unsigned int nindexes;
							if(fread(&nindexes, sizeof(uint32), 1, rf) != 1) { fclose(rf); return(false); }
							if(nindexes >0)
							{
								unsigned short *indexarray = new unsigned short[nindexes*sizeof(unsigned short)];
								if(fread(indexarray, sizeof(unsigned short), nindexes, rf) != nindexes) { fclose(rf); return(false); }
								for(int i=0;i<(int)nindexes; i++)
								{
									unsigned short val = indexarray[i];
									tempIndexArray.append(val);
								}
								delete indexarray;
							}

							// ---- vectors
							if(fread(&blockId, 4, 1, rf) != 1) {fclose(rf); return(false); }
							if(strcmp(blockId, "VERT") != 0) { fclose(rf); return(false); }
							if(fread(&blocksize, sizeof(int), 1, rf) != 1) { fclose(rf); return(false); }
							unsigned int nvectors;
							if(fread(&nvectors, sizeof(int), 1, rf) != 1) { fclose(rf); return(false); }
							float *vectorarray = 0;
							if(nvectors >0)
							{
								vectorarray = new float[nvectors*sizeof(float)*3];
								if(fread(vectorarray, sizeof(float)*3, nvectors, rf) != nvectors) { fclose(rf); return(false); }
							}
							// ----- liquit
							if(flags & 0x00001000)
							{
								// we have liquit -> not handled yet ... skip
								if(fread(&blockId, 4, 1, rf) != 1) { fclose(rf); return(false); }
								if(strcmp(blockId, "LIQU") != 0) { fclose(rf); return(false); }
								if(fread(&blocksize, sizeof(int), 1, rf) != 1) { fclose(rf); return(false); }
								fseek(rf, blocksize, SEEK_CUR);
							}

							for(unsigned int i=0, indexNo=0; indexNo<nvectors; indexNo++)
							{
								Vector3 v = Vector3(vectorarray[i+2], vectorarray[i+1], vectorarray[i+0]);
								i+=3;
								v = pModelPosition.transform(v);

								float swapy = v.y;
								v.y = v.x;
								v.x = swapy;

								tempVertexArray.append(v);
							}

							// ---- calculate triangles
							int rest = nindexes%3;
							if(rest != 0)
							{
								nindexes -= rest;
							}

							for(unsigned int i=0;i<(nindexes);)
							{
								Triangle t = Triangle(tempVertexArray[tempIndexArray[i+2]], tempVertexArray[tempIndexArray[i+1]], tempVertexArray[tempIndexArray[i+0]] );
								i+=3;
								++trianglecount;
								if(g>= startgroup && g <= endgroup)
								{
									gtree->insert(t);
								}
							}

							if(vectorarray != 0)
							{
								delete vectorarray;
							}

							if(gtree->size() >0)
							{
								gtree->balance();
								SubModel *sm = new SubModel(gtree);
								sm->setIndoorFlag( isindoor );
								#ifdef _ASSEMBLER_DEBUG
								if(::g_df) fprintf(::g_df,"group trianglies: %d, Tris: %d, Nodes: %d, gtree.triangles: %d\n", g, sm->getNTriangles(), sm->getNNodes(), gtree->memberTable.size());
								if(sm->getNTriangles() !=  gtree->memberTable.size())
								{
									if(::g_df) fprintf(::g_df,"ERROR !!!! group trianglies: %d, Tris: %d, Nodes: %d, gtree.triangles: %d\n", g, sm->getNTriangles(), sm->getNNodes(), gtree->memberTable.size());
								}
								#endif
								sm->setBasePosition(pModelPosition.iPos);
								pMainTree->insert(sm);
							}
							delete gtree;
						}
						fclose(rf);
						result = true;
					}
					return(result);
				}

				//=================================================================

				bool TileAssembler::fillModelIntoTree(AABSPTree<SubModel *> *pMainTree, const Vector3& pBasePos, std::string& pPos, std::string& pModelFilename)
				{
					bool result = false;
					ModelPosition modelPosition;
					getModelPosition(pPos, modelPosition);
					// all should be relative to object base position
					modelPosition.moveToBasePos(pBasePos);

					modelPosition.init();

					if(readRawFile(pModelFilename,  modelPosition, pMainTree))
					{
						result = true;
					}
					else
						printf("readRawFile(%s) failed.\n", pModelFilename.c_str());

					return result;
				}

				//=================================================================
				void TileAssembler::getModelPosition(std::string& pPosString, ModelPosition& pModelPosition)
				{
					float vposarray[3];
					float vdirarray[3];
					float scale;

					size_t spos = pPosString.find_first_of('#');
					std::string stripedPosString = pPosString.substr(spos+1,pPosString.length());

					sscanf(stripedPosString.c_str(), "%f,%f,%f_%f,%f,%f_%f",
						&vposarray[0],&vposarray[1],&vposarray[2],
						&vdirarray[0],&vdirarray[1],&vdirarray[2],
						&scale);

					pModelPosition.iPos = Vector3(vposarray[0], vposarray[1], vposarray[2]);
					pModelPosition.iDir = Vector3(vdirarray[0], vdirarray[1], vdirarray[2]);
					pModelPosition.iScale = scale;

				}
				//==========================================

			}											   // VMAP
